﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Castle.Components.Common.TemplateEngine;

namespace mst_regex
{
    public class DseDailyMarketStatisticsParser : IDailyMarketStatisticsParser
    {
        private readonly Dictionary<MstSegmentType, string> _mstSegments = new Dictionary<MstSegmentType, string>();

        private readonly Dictionary<MstSegmentType, SegmentProcessor> _segmentProcessorsMap =
            new Dictionary<MstSegmentType, SegmentProcessor>();

        public DseDailyMarketStatisticsParser()
        {
            _segmentProcessorsMap.Add(MstSegmentType.Header,
                                      new SegmentProcessor
                                          {
                                              Breakpoint = @"prices in public transactions",
                                              Processor = ParseHeader
                                          });
            _segmentProcessorsMap.Add(MstSegmentType.PublicTransactions,
                                      new SegmentProcessor
                                          {
                                              Breakpoint = @"prices in spot transactions",
                                              Processor = ParsePublicTransactions
                                          });
            _segmentProcessorsMap.Add(MstSegmentType.SpotTransactions,
                                      new SegmentProcessor
                                          {
                                              Breakpoint = @"prices in oddlot transactions",
                                              Processor = ParseSpotTransactions
                                          });
            _segmentProcessorsMap.Add(MstSegmentType.OddlotTransactions,
                                      new SegmentProcessor
                                          {
                                              Breakpoint = @"scrips traded in oddlot",
                                              Processor = ParseOddlotTransactions
                                          });
            _segmentProcessorsMap.Add(MstSegmentType.Footer,
                                      new SegmentProcessor
                                          {
                                              Breakpoint = string.Empty,
                                              Processor = ParseFooter
                                          });
        }

        #region IDailyMarketStatisticsParser Members

        public StockExchangeType StockExchange
        {
            get { return StockExchangeType.Dse; }
        }

        public DailyMarketStatistics ParseMakertStatisticsData(string data, DateTime effectiveDate)
        {
            SplitMstDataIntoSegments(data.Trim());
            var stats = new DailyMarketStatistics(StockExchangeType.Dse, effectiveDate);
            ParseStatistics(ref stats);
            return stats;
        }

        #endregion

        private void SplitMstDataIntoSegments(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                return;
            }

            string dataLow = data.ToLower();
            int lastIndex = 0;
            foreach (MstSegmentType segment in Enum.GetValues(typeof (MstSegmentType)))
            {
                string breakpoint = _segmentProcessorsMap[segment].Breakpoint;
                int findIndex = string.IsNullOrEmpty(breakpoint) ? -1 : dataLow.IndexOf(breakpoint, lastIndex);
                if (findIndex != -1)
                {
                    string extract = data.Substring(lastIndex, findIndex - lastIndex).Trim();
                    _mstSegments.Add(segment, extract);
                    lastIndex = findIndex + breakpoint.Length;
                }
                else if (segment == MstSegmentType.Footer)
                {
                    string extract = data.Substring(lastIndex);
                    _mstSegments.Add(segment, extract);
                }
            }
        }

        private void ParseStatistics(ref DailyMarketStatistics stats)
        {
            foreach (var processor in
                from MstSegmentType segment in Enum.GetValues(typeof (MstSegmentType))
                select _segmentProcessorsMap[segment].Processor)
            {
                processor(ref stats);
            }
        }

        private static decimal TakaToCrore(decimal taka)
        {
            return (taka/10000000.0m);
        }

        #region Processor functions

        private void ParseHeader(ref DailyMarketStatistics stats)
        {
            string data = _mstSegments[MstSegmentType.Header];

            // Get the market date
            stats.EffectiveDate = DateTime.Today;
            var match = new Regex(@"(?<year>\d{2,4})-(?<month>\d{1,2})-(?<day>\d{1,2})").Match(data);
            if (match.Success)
            {
                int year, month, day = 0;
                if (int.TryParse(match.Groups["year"].Value, out year) &&
                    int.TryParse(match.Groups["month"].Value, out month) &&
                    int.TryParse(match.Groups["day"].Value, out day))
                {
                    stats.EffectiveDate = new DateTime(year, month, day);
                }
            }

            // Get the general indices
            var rexIndices =
                new Regex(
                    @"[\w\d\s]+\((?<title>[\w\d]+)\)\s+(?<index>[\d\.]+)\s+(?<change_points>[\d\.\-]+)\s+(?<change_percents>[\d\.\-]+)",
                    RegexOptions.Compiled);
            match = rexIndices.Match(data);
            while (match.Success)
            {
                decimal index, changePoints, changePercents;
                string title = match.Groups["title"].Value;
                decimal.TryParse(match.Groups["index"].Value, out index);
                decimal.TryParse(match.Groups["change_points"].Value, out changePoints);
                decimal.TryParse(match.Groups["change_percents"].Value, out changePercents);
                stats.SetIndexValues(title, index, changePoints, changePercents);

                match = match.NextMatch();
            }

            // Session information
            // A. NO. OF TRADES                :                 210841
            match = new Regex(@"TRADES\s+:\s+(\d+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                int trades;
                int.TryParse(match.Groups[1].Value, out trades);
                stats.Trades = trades;
            }

            // B. VOLUME(Nos.)                 :               67069971
            match = new Regex(@"VOLUME[.()\w\s]+:\s+([\d]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                int volume;
                int.TryParse(match.Groups[1].Value, out volume);
                stats.Volume = volume;
            }

            // C. VALUE(Tk)                    :         18873757475.85
            match = new Regex(@"VALUE\(Tk\)\s+:\s+([\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal val;
                if (decimal.TryParse(match.Groups[1].Value, out val))
                {
                    stats.ValueInMillions = TakaToCrore(val);
                }
            }

            // Market capital
            //  1. EQUITY                          :       2340403535964.55
            match = new Regex(@"EQUITY\s+:\s+([\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal val;
                if (decimal.TryParse(match.Groups[1].Value, out val))
                {
                    stats.MarketCapEquity = TakaToCrore(val);
                }
            }

            //  2. MUTUAL FUND                     :         26533187500.00
            match = new Regex(@"FUND\s+:\s+([\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal val;
                if (decimal.TryParse(match.Groups[1].Value, out val))
                {
                    stats.MarketCapMutualFunds = TakaToCrore(val);
                }
            }

            //  3. DEBT SECURITIES                 :        398309048550.00
            match = new Regex(@"SECURITIES\s+:\s+([\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal val;
                if (decimal.TryParse(match.Groups[1].Value, out val))
                {
                    stats.MarketCapDebt = TakaToCrore(val);
                }
            }

            //  TOTAL                           :       2765245772014.55
            match = new Regex(@"TOTAL\s+:\s+([\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal val;
                if (decimal.TryParse(match.Groups[1].Value, out val))
                {
                    stats.MarketCapTotal = TakaToCrore(val);
                }
            }
        }

        private void ParsePublicTransactions(ref DailyMarketStatistics stats)
        {
            var lines = _mstSegments[MstSegmentType.PublicTransactions].Split(new[] {'\r', '\n'},
                                                                              StringSplitOptions.RemoveEmptyEntries);
            ParseStockAndSpotListing(lines, stats.PublicTransactions);
        }

        private void ParseSpotTransactions(ref DailyMarketStatistics stats)
        {
            var lines = _mstSegments[MstSegmentType.SpotTransactions].Split(new[] {'\r', '\n'},
                                                                            StringSplitOptions.RemoveEmptyEntries);
            ParseStockAndSpotListing(lines, stats.SpotTransactions);
        }

        private void ParseOddlotTransactions(ref DailyMarketStatistics stats)
        {
            var lines = _mstSegments[MstSegmentType.OddlotTransactions].Split(new[] {'\r', '\n'},
                                                                              StringSplitOptions.RemoveEmptyEntries);
            ParseOddlotListing(lines, stats.OddlotTransactions);
        }

        private static void ParseStockAndSpotListing(IEnumerable<string> lines, StockPositions positions)
        {
            //Instr Code     Open     High      Low    Close   %Chg  Trade    Volume Value(Mn)
            //ABBANK      1360.00  1399.00  1352.25  1393.50    2.84   8826   628635   868.870
            //ACI          396.50   405.00   394.10   396.00    -.27    425    54650    21.655
            var rx = new Regex(@"^(\S+)\s+(\s+[\d\.\-]+){8}$",
                               RegexOptions.Multiline | RegexOptions.Compiled);
            foreach (var line in lines)
            {
                var match = rx.Match(line);
                if (match.Success)
                {
                    decimal high = 0m, low = 0m, open = 0m, close = 0m, change = 0m, value = 0m;
                    int trades = 0, volume = 0;
                    string ticker = null;

                    for (int grpNum = 1; grpNum < match.Groups.Count; grpNum++)
                    {
                        if (grpNum == 1)
                        {
                            ticker = match.Groups[grpNum].Captures[0].Value.Trim().ToUpper();
                        }
                        else
                        {
                            var captureCtr = (int) StockColumns.Open;
                            foreach (Capture capture in match.Groups[grpNum].Captures)
                            {
                                switch ((StockColumns) captureCtr)
                                {
                                    case StockColumns.Open:
                                        decimal.TryParse(capture.Value.Trim(), out open);
                                        break;
                                    case StockColumns.High:
                                        decimal.TryParse(capture.Value.Trim(), out high);
                                        break;
                                    case StockColumns.Low:
                                        decimal.TryParse(capture.Value.Trim(), out low);
                                        break;
                                    case StockColumns.Close:
                                        decimal.TryParse(capture.Value.Trim(), out close);
                                        break;
                                    case StockColumns.Change:
                                        decimal.TryParse(capture.Value.Trim(), out change);
                                        break;
                                    case StockColumns.Trades:
                                        int.TryParse(capture.Value.Trim(), out trades);
                                        break;
                                    case StockColumns.Volume:
                                        int.TryParse(capture.Value.Trim(), out volume);
                                        break;
                                    case StockColumns.Value:
                                        decimal.TryParse(capture.Value.Trim(), out value);
                                        break;
                                }
                                captureCtr++;
                            }
                        }
                    }

                    if (trades > 0)
                        positions.AddRecord(ticker, open, close, high, low, change, value, trades, volume);
                }
            }
        }

        private static void ParseOddlotListing(IEnumerable<string> lines, OddlotStockPositions positions)
        {
            //Instr Code    Max Price    Min Price    Trades    Quantity    Value(In Mn)
            //ABBANK          1155.00      1025.00         8          12            .013
            //AFTABAUTO        285.00       260.00        47         939            .254
            var rx = new Regex(@"^(\S+)\s+(\s+[\d\.\-]+){5}$",
                               RegexOptions.Multiline | RegexOptions.Compiled);
            foreach (var match in lines.Select(line => rx.Match(line)).Where(match => match.Success))
            {
                decimal max = 0m, min = 0m, value = 0m;
                int trades = 0, quantity = 0;
                string symbol = null;

                for (var grpNum = 1; grpNum < match.Groups.Count; grpNum++)
                {
                    if (grpNum == 1)
                    {
                        symbol = match.Groups[grpNum].Captures[0].Value.Trim().ToUpper();
                    }
                    else
                    {
                        var captureCtr = (int) OddlotColumns.Max;
                        foreach (Capture capture in match.Groups[grpNum].Captures)
                        {
                            switch ((OddlotColumns) captureCtr)
                            {
                                case OddlotColumns.Max:
                                    decimal.TryParse(capture.Value.Trim(), out max);
                                    break;
                                case OddlotColumns.Min:
                                    decimal.TryParse(capture.Value.Trim(), out min);
                                    break;
                                case OddlotColumns.Trades:
                                    int.TryParse(capture.Value.Trim(), out trades);
                                    break;
                                case OddlotColumns.Quantity:
                                    int.TryParse(capture.Value.Trim(), out quantity);
                                    break;
                                case OddlotColumns.Value:
                                    decimal.TryParse(capture.Value.Trim(), out value);
                                    break;
                            }
                            captureCtr++;
                        }
                    }
                }

                if (trades > 0)
                    positions.AddRecord(symbol, max, min, value, trades, quantity);
            }
        }

        private void ParseFooter(ref DailyMarketStatistics stats)
        {
            var data = _mstSegments[MstSegmentType.Footer];

            // DSI           5160.05354    5278.85214
            var match = new Regex(@"DSI\s+(?<open>[\d\.]+)\s+(?<close>[\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal open;
                decimal.TryParse(match.Groups["open"].Value, out open);
                stats.DsiOpen = open;
            }

            // DS20          3683.87134    3763.75670
            match = new Regex(@"DS20\s+(?<open>[\d\.]+)\s+(?<close>[\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal open;
                decimal.TryParse(match.Groups["open"].Value, out open);
                stats.Ds20Open = open;
            }

            // DGEN          6217.07615    6354.68333
            match = new Regex(@"DGEN\s+(?<open>[\d\.]+)\s+(?<close>[\d\.]+)", RegexOptions.IgnoreCase).Match(data);
            if (match.Success)
            {
                decimal open;
                decimal.TryParse(match.Groups["open"].Value, out open);
                stats.DgenOpen = open;
            }
        }

        #region Nested type: OddlotColumns

        private enum OddlotColumns
        {
            Max = 0,
            Min,
            Trades,
            Quantity,
            Value
        }

        #endregion

        #region Nested type: StockColumns

        private enum StockColumns
        {
            Open = 0,
            High,
            Low,
            Close,
            Change,
            Trades,
            Volume,
            Value
        }

        #endregion

        #endregion

        #region Nested type: MstSegmentType

        private enum MstSegmentType
        {
            Header,
            PublicTransactions,
            SpotTransactions,
            OddlotTransactions,
            Footer
        }

        #endregion

        #region Nested type: ProcessDataDelegate

        private delegate void ProcessDataDelegate(ref DailyMarketStatistics stats);

        #endregion

        #region Nested type: SegmentProcessor

        private struct SegmentProcessor
        {
            public string Breakpoint;
            public ProcessDataDelegate Processor;
        }

        #endregion
    }
}